package wikibooks;

import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Converts and outputs to .tex files a book based on
 * <code>BookSettings bs</code> and <code>ConversionSettings cs</code>.
 * Instantiate with
 * <code>new WikibookConverter(BookSettings bs, ConversionSettings cs)</code>
 * or another constructor and convert with <code>convertBook()</code>. Other
 * public methods are available for specific functions, such as getting a set of
 * authors (<code>SortedSet<String> getBookAuthors()</code>) or downloading
 * all images used in the book (<code>downloadImages()</code>).
 */
public class WikibookConverter {

    private BookSettings bs;
    private ConversionSettings cs;
    private ResourceBundle messages;
    /**
     * width of SVG image converted to PNG (in pixels).
     */
    private final static int SVG_SIZE = 400;

    //private ResourceBundle conversionProperties;
    /**
     * Constructor which only changes the title and path from their default
     * values. Use <code>setChaptersFromPage(String pageName)</code> to create
     * the list of chapters if necessary.
     * 
     * @param title
     *            The book's title.
     */
    public WikibookConverter(String title) {
        this(title, false, (String[]) null);
    }

    public WikibookConverter(String title, String... chapters) {
        this(title, false, chapters);
    }

    public WikibookConverter(String title, boolean generateAllFiles) {
        this(title, generateAllFiles, (String[]) null);
    }

    public WikibookConverter(String title, boolean generateAllFiles,
            String... chapters) {
        this(new BookSettings(title, generateAllFiles, chapters),
                new ConversionSettings());
    }

    public WikibookConverter(BookSettings bs, ConversionSettings cs) {
        this.bs = bs;
        this.cs = cs;

        Logger.getLogger("WikibookConverter").info("read toc: " + bs.readTocFromFile);

        //conversionProperties = ResourceBundle.getBundle("conversion");
        //bs.setServer(conversionProperties.getString("server"));
        //Util.setServer(bs.getServer());
        messages = ResourceBundle.getBundle("messages", new Locale(bs.getLanguage()));
        bs.setAuthor(messages.getString("Wikibooks_contributors"));
    }

    /**
     * Convert chapters and create .tex files for the book based on the settings
     * given in the constructor.
     */
    public void convertBook() {
        if (bs.chapters().isEmpty()) {
            if (bs.readTocFromFile) {
                setChaptersFromFile(bs.title() + ".txt");
            } else if (!bs.createChaptersFromFile) {
                setChaptersFromPage(bs.chapterListLocation());
            }
        }

        new File(bs.path()).mkdir();
        new File(bs.path() + "misc/").mkdir();
        new File(bs.path() + "chapters/").mkdir();
        new File(bs.path() + "appendices/").mkdir();
        new File(bs.path() + "images/").mkdir();
        try {
            new File(bs.path() + "appendices/gfdl.tex").createNewFile();
            // TODO add proper GFDL
        } catch (IOException e) {
            Log.addError("Could not create gfdl.tex.");
        }

        if (bs.generateChapters) {
            convertChapters();
        }
        if (bs.generateMainFile) {
            generateMainFile();
        }
        if (bs.generateTitlePage) {
            generateTitlePage();
        }
        if (bs.generateStyle) {
            generateStyle();
        }
        if (bs.generateDocInfo) {
            generateDocInfo();
        }
        if (bs.generateKileProjectFile) {
            generateKileProjectFile();
        }
        //if (bs.generateWikiPage)            generateWikiPage();
        if (bs.getImages) {
            downloadImages();
        }

        if (!cs.silentMode) { // completion message
            String message = "Completed; see \"" + new File(bs.path()).getAbsolutePath().replace(" ", "_") + "\"";
            Log.addMessage(message);
            System.out.print(message + "\n" + (Log.errors().length > 0 ? Log.errors().length + " error(s), " : "") + (Log.warnings().length > 0 ? Log.warnings().length + " warning(s), " : "") + (Log.errors().length > 0 || Log.warnings().length > 0 ? "see ./log.txt" : "") + "\n");
        }
    }

    /**
     * Converts chapters listed in BookSettings.chapters(). Add chapters with
     * the constructor
     * <code>WikibookConverter(String title, String... chapters)</code>, with
     * <code>BookSettings.addChapterFromFile(String chapter, String filename)</code>,
     * or with <code>BookSettings.addChapters(String... chapters)</code>
     */
    void convertChapters() {
        if (!cs.silentMode) {
            System.out.print("Converting chapters: ");
        }

        WikicodeConverter wikicodeConverter = new WikicodeConverterImpl(bs, cs);
        for (String[] chapter : bs.chapters()) { // convert chapters
            try {
                if (!cs.silentMode) {
                    System.out.print(chapter[1] + ", ");
                }

                String title = chapter[1];
                if (title.contains("/")) {
                    title = title.substring(title.indexOf("/"));
                }
                String shortTitle = chapter[2];

                ChapterConverter chapterConverter;
                if (bs.createChaptersFromFile) {
                    chapterConverter = new ChapterConverter(bs, cs, title, shortTitle, new File(title + ".txt"));
                } else {
                    chapterConverter = new ChapterConverter(bs, cs, createFullChapterName(bs.title(), chapter[1]), shortTitle);
                }
                chapterConverter.setWikicodeConverter(wikicodeConverter);
                String latex = chapterConverter.convert();


                Util.writeToFile(latex,
                        new File(bs.path() + "chapters/" + chapter[0] + ".tex"));
            } 
            // create an empty file if the process fails, so that the LaTeX translation may proceed without this chapter
            catch (Throwable e) {
                try {
                    new File(bs.path() + "chapters/" + chapter[0] + ".tex").createNewFile();
                } catch (IOException io) {
                    Logger.getLogger("WikibookConverter").log(Level.SEVERE, "", io);
                }

                String errorType = "Misc error";
                if (e instanceof StringIndexOutOfBoundsException) {
                    errorType = "Formatting error";
                }
                if (e instanceof StackOverflowError) {
                    errorType = "Infinite loop";
                }

                String error = errorType + " in chapter " + chapter[1] + " (file " + bs.path() + "chapters/" + chapter[0] + ".tex). Error: " + e.toString() + "\n\nStacktrace:\n";
                for (StackTraceElement element : e.getStackTrace()) {
                    error += element.toString() + "\n";
                }
                Log.addError(error);
                if (cs.silentMode) {
                    break;
                }
            }
        }

        if (!cs.silentMode) {
            System.out.print("- done!\n\n");
        }
    }

    /**
     * Download images listed in ConversionSettings.images() to "path/images,"
     * where "path" is BookSettings.path().
     */
    void downloadImages() {
        if (bs.useTitlePageLogo) { // add titlepage logo
            String imageURL = cs.logoPath;
            String image = Util.getHTML(imageURL, "Latin1");
            Util.writeToFile(image, "Latin1", new File(bs.path() + "misc/logo.png"));
        }

        //System.err.printf("%b %d\n", bs.generateChapters, cs.images().size());
        // bylo: !bs.
        if (bs.generateChapters && cs.images().size() == 0) {
            setImageSet();
        }

        Log.addMessage("Images found in chapters:\n\n" + cs.images());

        if (!cs.silentMode) {
            System.out.print("Downloading images: ");
        }

        int imageNumber = 1;
        for (String imageName : cs.images()) {
            if (!cs.silentMode) {
                System.out.print(imageNumber + ", ");
            }
            byte[] image = downloadImage(imageName);
            if (image != null) {
                imageName = Util.toSafeImageName(imageName);
                Util.writeToFile(image, new File(bs.path() + "images/" + imageName));
            }
            imageNumber++;
        }

        if (!cs.silentMode) {
            System.out.print("- done!\n\n");
        }
    }

    /**
     * Download image either from Wikibooks or Commons.
     * 
     * @param imageName
     *            image's title and extension (ie "foo.jpg")
     * @return image code in Latin1 (write this to a file (ie
     *         <code>Util.writeToFile(image, "Latin1", new File("images/" + imageName));</code>,
     *         where image is returned by this method))
     */
    byte[] downloadImage(String imageName) {
        imageName = imageName.trim().replace(" ", "_");
        String imagePageURL = String.format("http://%s/wiki/Image:%s", bs.getServer(), imageName);
        String HTML = Util.getHTML(imagePageURL, "UTF8");

        if (HTML == null) {
            return null;
        }

        int beginDiv = HTML.indexOf("<div class=\"fullImageLink\"");
        int endDiv = HTML.indexOf("</div>", beginDiv);
        if (beginDiv != -1 && endDiv != -1) {
            // <div class="fullImageLink" id="file"><a href="IMAGEURL"> ... </a> ... </div>
            HTML = HTML.substring(beginDiv, endDiv);
        } else {
            // TODO better fallback
            Log.addError("Could not get image " + imageName);
            return null;
        }
        String imageURL = null;
        if (HTML.indexOf("<a href=\"") != -1){
            HTML = HTML.substring(HTML.indexOf("<a href=\""));
            imageURL = HTML.substring(9, HTML.indexOf("\">"));
        }
        else if (HTML.indexOf("<img ") != -1){
            HTML = HTML.substring(HTML.indexOf("src=\""));
            imageURL = HTML.substring("src=\"".length(), HTML.indexOf("\"", "src=\"".length()));
        }
        else {
            Log.addError("Could not get image " + imageName);
            return null;
        }
        
        if (imageURL.startsWith("/")){
            imageURL = "http://" + bs.getServer() + imageURL;
        }

        if (imageName.endsWith(".svg") || imageName.endsWith(".SVG")) { // convert svg
            imageURL += String.format("/%dpx-%s.png", SVG_SIZE, imageName);
            imageURL = imageURL.replace("/commons/", "/commons/thumb/");
            imageName = imageName.replace("svg", "png").trim();
            imageName = imageName.replace("SVG", "png").trim();
        } else if (!imageName.endsWith("jpg") && !imageName.endsWith("JPG") && // Incompattible extension
                !imageName.endsWith("png") && !imageName.endsWith("PNG") &&
                !imageName.endsWith("jpeg") && !imageName.endsWith("JPEG")) {
            Log.addError("Incompatible extension for image " + imageName + " (use jpg, png, or svg)");
        }

        return Util.getBytes(imageURL);
    }

    /**
     * Adds images to ConversionSettings.images() by searching through the
     * wikicode of each chapter in BookSettings.chapters().
     */
    void setImageSet() {
        Logger log = Logger.getLogger("WikibookConverter");

        if (!cs.silentMode) {
            System.out.print("Adding images to image set from chapters: ");
        }

        for (String[] chapter : bs.chapters()) {
            if (!cs.silentMode) {
                System.out.print(chapter[1] + ", ");
            }

            String wikicode = Util.downloadWikicode(createFullChapterName(bs.title(), chapter[1]));
            addImagesFromWikicode(wikicode);

        }
        if (!cs.silentMode) {
            System.out.print("- done!\n\n");
        }
    }

    /**
     * searches wikicode for images and remembers their names.
     * @param wikicode whole wikicode
     */
    void addImagesFromWikicode(String wikicode) {
        Pattern p = Pattern.compile("\\[\\[(Image|Grafika|Bild):([^\\|\\]]+\\.(png|svg|gif|jpeg|jpg|PNG|SVG|GIF|JPEG|JPG))");
        Matcher m = p.matcher(wikicode);

        while (m.find()) {
            String image = m.group(2);
            image = image.trim();

            if (!image.equals("")) {
                cs.addImages(image);
            }

        }
    }

    /**
     * Looks throug the history page of each chapter in BookSettings.chapters(),
     * and adds authors to the returned set.
     * 
     * @return set of the book's authors
     */
    SortedSet<String> getBookAuthors() {
        HashSet<String> users = new HashSet<String>();

        if (!cs.silentMode) {
            System.out.print("Getting authors: ");
        }

        if (!bs.title().equals("")) {
            users.addAll(getPageAuthors(bs.title()));
        }

        for (String[] chapter : bs.chapters()) {
            if (!cs.silentMode) {
                System.out.print(chapter[1] + ", ");
            }

            users.addAll(getPageAuthors(createFullChapterName(bs.title(), chapter[1])));
        }
        if (!cs.silentMode) {
            System.out.print("- done!\n\n");
        }
        return new TreeSet<String>(users);
    }

    /**
     * Adds authors listed in the the hisotry page of pageName to set and
     * returns.
     * 
     * @param pageName
     *            pageName whose history page to search
     * @return set of authors of String pageName
     */
    Set<String> getPageAuthors(String pageName) {
        HashSet<String> users = new HashSet<String>();

        String URL = String.format("http://%s%sindex.php?title=%s&action=history",
                bs.getServer(), bs.getMainDir(), Util.toSafePageName(pageName));
        String HTML = Util.getHTML(URL, "UTF8");

        //<ul id="pagehistory"> ... authors ... </ul>
        if (HTML != null && HTML.contains("<ul id=\"pagehistory\">") && HTML.contains("</ul>")) {
            HTML = HTML.substring(HTML.indexOf("<ul id=\"pagehistory\">"),
                    HTML.indexOf("</ul", HTML.indexOf("<ul id=\"pagehistory\">")));
        } else {
            Log.addError("Could not find <ul id=\"pagehistory\"> ... " + "</ul> on history page of " + pageName + "\n");
            HTML = "";
        }
        // user = <span class='history-user'><a href="/wiki/User:Hagindaz" title="User:Hagindaz">Hagindaz</a> ... </span>
        String histSpanClass = "<span class='history-user'>";
        if (HTML.indexOf(histSpanClass) == -1){ // older mediawiki versions use class='user'
            histSpanClass = "<span class='user'>";
        }

        int begin = HTML.indexOf(histSpanClass);
        int end = HTML.indexOf("</span>", begin);
        while (begin != -1 && end != -1) {
            String user = HTML.substring(begin, end);
            user = user.substring(user.indexOf("<a href"), user.indexOf("</a>"));
            user = user.substring(user.indexOf("\">") + 2);
            users.add(user);

            begin = HTML.indexOf(histSpanClass, end);
            end = HTML.indexOf("</span>", begin);
        }

        return users;
    }

    private String createFullChapterName(String bookTitle, String chapterTitle) {        
        String retval = bookTitle.equals("") ? chapterTitle : bookTitle + "/" + chapterTitle;
        retval = retval.replace(" ", "_");
        return retval;
    }

    /**
     * Adds names of subpages of the book found on String page to
     * BookSettings.chapters()
     * 
     * @param page
     *            page through which to look through for book subpage links
     */
    void setChaptersFromPage(String page) {
        Logger.getLogger("WikibookConverter").info("reading toc from web");

        String wikicode = Util.downloadWikicode(page.replace(" ", "_"));

        setChapters(wikicode, page);
    }

    void setChaptersFromFile(String filename) {
        Logger.getLogger("WikibookConverter").info("reading toc from file");

        setChapters(Util.getStringFromFile(filename), filename);
    }

    /**
     * Fills BookSettings.chapters() with names of subpages.
     * @param wikicode wikicode of page with subpage links
     * @param source name of source - URL of webpage or filename for file
     */
    void setChapters(String wikicode, String source) {
        int begin = wikicode.indexOf("[[");
        int end = wikicode.indexOf("]]", begin);
        while (begin != -1 && end != -1) {
            String chapter = wikicode.substring(begin, end + 2);
            chapter = chapter.replace("_", " ");
            String shortTitle = "";
            // check if subpage
            if ((bs.title().isEmpty() || chapter.indexOf("/") == 2 || chapter.indexOf(bs.title()) == 2 || chapter.indexOf("../") == 2) &&
                    !chapter.contains("Image:") && !chapter.contains("Category:") && chapter.indexOf(":") != 5 &&
                    !chapter.contains("Grafika:") && !chapter.contains("Kategoria:") &&
                    !chapter.contains("Print") && !chapter.contains("Wersja do druku") &&
                    !chapter.contains("Q&A") &&
                    !chapter.contains("Manual of Style") && !chapter.contains("All Versions") &&
                    !chapter.contains("All pages") &&
                    !chapter.contains("Cover") && !chapter.contains("Okładka") &&
                    !chapter.contains("Policy")) {
                // cleanup
                chapter = chapter.replace("/]]", "");
                chapter = chapter.replace("[[/", "");
                chapter = chapter.replace("]]", "");
                chapter = chapter.replace("[[", "");
                chapter = chapter.trim();
                if (chapter.contains("#")) {
                    chapter = chapter.substring(0, chapter.indexOf("#"));
                }
                if (chapter.contains("|")) {
                    shortTitle = chapter.substring(chapter.indexOf("|") + 1);
                    chapter = chapter.substring(0, chapter.indexOf("|"));
                }
                if (chapter.indexOf(bs.title() + "/") == 0) {
                    chapter = chapter.replace(bs.title() + "/", "");
                }
                if (chapter.endsWith("/")) {
                    chapter = chapter.substring(0, chapter.length() - 1);
                }

                // add to ArrayList <String> chapters
                bs.addChapter(chapter, shortTitle);
            }
            begin = wikicode.indexOf("[[", end);
            end = wikicode.indexOf("]]", begin);
        }

        // write to log
        StringBuilder message = new StringBuilder("Chapters of [[").append(bs.title());
        message.append("]] found on [[").append(source).append("]]:\n\n");
        for (String[] chapter : bs.chapters()) {
            message.append(chapter[1]).append(", ");
        }
        Log.addMessage(message.toString());
    }

    /**
     * Creates the file path/main.tex, where path is BookSettings.path().
     * main.tex links to all the chapters. Run <code>pdflatex main</code> to
     * convert document to PDF.
     */
    void generateMainFile() {
        final String indent = cs.indent();

        final boolean isBook = bs.article == null;

        String documentParams = "final,twoside,onecolumn,openright,10pt";
        if (bs.getLanguage().equals("pl")) {
            documentParams += ",a4paper";
        }
        if (isBook) {
            documentParams += ",titlepage";
        }

        String text = String.format("\\documentclass[%s]{%s}\n", documentParams, isBook ? "book" : "article");
        text += "\\usepackage{./misc/style}\n" + "\\begin{document}\n\n";

        text += (bs.generateTitlePage ? "\\include{./misc/titlepage}"
                : "\\title{" + bs.getDisplayTitle() + "}\n" + "\\author{" + bs.author() + "}\n" + "\\maketitle") + "\n\n";

        if (isBook) {
            text += "\\cleardoublepage" + indent + indent + "% page format for front matter - roman numerals and no headings\n";
            //text += "\\pagenumbering{roman}\n" + "\\setcounter{page}{1}\n";
            text += "\\pagestyle{plain}\n\n";

            text += "\\tableofcontents\n" + "%\\listoffigures\n" + "%\\listoftables\n" + (cs.codeLanguage() != null ? "\\lstlistoflistings\n" : "") + "\n";

            text += "\\clearpage\n\\thispagestyle{empty}" + indent + indent + "% Blank page before main matter\n\n";
            text += "\\cleardoublepage" + indent + indent + "% page format for the rest of the book - start on page 1 and " +
                    "include headings\n";
            //text += "\\pagenumbering{arabic}\n" + "\\setcounter{page}{1}\n";
            text += "\\pagestyle{headings}\n\n";
        }

        final String includeCommand = isBook ? "include" : "input";
        for (String[] chapter : bs.chapters()) {
            text += String.format("\\%s{./chapters/%s}\n", includeCommand, chapter[0]);
        }

        text += "\n\\appendix\n" + "\\include{./appendices/docinfo}\n" + "\\include{./appendices/gfdl}\n\n";
        text += "%\\addcontentsline{toc}{chapter}{index}\n" + "%\\printindex\n\n" + "\\end{document}";

        Util.writeToFile(text, new File(bs.path() + "main.tex"));
    }

    /**
     * Creates the file path/misc/titlepage.tex, where path is
     * BookSettings.path().
     */
    void generateTitlePage() {
        // TODO use StringBuilder instead of String for performance

        final String indent = cs.indent();
        String text = "\\begin{titlepage}\n" + indent + "\\begin{minipage}{365pt}\n" + indent + indent +
                "\\begin{center}\n" + indent + indent + indent + "{\\titlefont " + bs.getDisplayTitle() + "}\n\n" + indent + indent + indent +
                "\\rule[5pt]{365pt}{0.5mm}\n\n";
        text += indent + indent + indent + "\\bigskip\n" + indent + indent + indent +
                "{\\large \\textit{" + String.format(messages.getString("by_authors"), bs.author()) + "}}\n";
        text += indent + indent + "\\end{center}\n\n" + indent + indent + "\\addvspace{375pt}\n\n";

        if (bs.useTitlePageLogo) {
            text += indent + indent + "\\begin{minipage}{0.4\\textwidth}\n" + indent + indent + indent + "\\begin{flushleft}\n" +
                    indent + indent + indent + indent + "\\includegraphics[width=120pt]{./misc/logo}" +
                    "\n" + indent + indent + indent + "\\end{flushleft}\n" + indent + indent + "\\end{minipage}\n";
        }
        text += indent + indent + "\\begin{minipage}{0.6\\textwidth}\n" + indent + indent + indent + "\\begin{flushleft}\n" +
                indent + indent + indent + "\\textit{{\\large " + messages.getString("created_on_Wikibooks") + "}\\\\\n" +
                indent + indent + indent + indent + "{\\large " + messages.getString("open_textbook_collection") + "}}\n" +
                indent + indent + indent + "\\end{flushleft}\n" + indent + indent + "\\end{minipage}\n";
        text += indent + "\\end{minipage}\n" + "\\end{titlepage}\n\n";

        text += "\\thispagestyle{empty}\n" + "\\indent\n" + "\\vfill\n\n" + "\\noindent\n" +
                String.format("%s.\n\n", messages.getString("copyright_contributors"));
        text += "\\bigskip\n" + "\\noindent\n";

        text += "Permission is granted to copy, distribute and/or modify this document under the terms " +
                "of the GNU Free Documentation License, Version 1.2 or any later version published by the " +
                "Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover " +
                "Texts. A copy of the license is included in the section entitled ``GNU Free Documentation " +
                "License''.";

        if (bs.getLanguage().equals("pl")) {
            text += "\n\n\\addvspace{10pt}\n\n\\noindent\n";
            text += "Udziela się zezwolenia na kopiowanie, rozpowszechnianie i/lub modyfikację treści\n" +
                    "artykułów polskich Wikibooks zgodnie z zasadami Licencji GNU Wolnej Dokumentacji\n" +
                    "(GNU Free Documentation License) w wersji 1.2 lub dowolnej późniejszej opublikowanej\n" +
                    "przez Free Software Foundation; bez Sekcji Niezmiennych, Tekstu na Przedniej Okładce\n" +
                    "i bez Tekstu na Tylnej Okładce. Kopia tekstu licencji znajduje się w części zatytułowanej\n" +
                    "``GNU Free Documentation License''.\n\n";
            text += "\\addvspace{10pt}\n\n";
            text += "\\noindent\n" +
                    "Dodatkowe objaśnienia są podane w dodatku ``Dalsze wykorzystanie tej książki''.\n\n";
            text += "\\addvspace{10pt}\n\n";
            text += "\\noindent\n" +
                    "Wikibooks nie udziela żadnych gwarancji, zapewnień ani obietnic dotyczących poprawności\n" +
                    "publikowanych treści. Nie udziela też żadnych innych gwarancji, zarówno jednoznacznych,\n" +
                    "jak i dorozumianych.";
        }

        Util.writeToFile(text, new File(bs.path() + "misc/titlepage.tex"));
    }
    
    String printPackages(String[][] packagesSpec) {
        final int commentIndent = 32;
        StringBuilder retval = new StringBuilder(100);
        for (String[] packageSpec : packagesSpec) {
            String name = "", options = "", comment = "";
            if (packageSpec.length > 0) {
                name = packageSpec[0];
            }
            if (packageSpec.length > 1) {
                options = packageSpec[1];
            }
            if (packageSpec.length > 2) {
                comment = packageSpec[2];
            }
            
            String usepackage = "";
            if (!name.isEmpty()) {
                usepackage += "\\usepackage";
                if (!options.isEmpty()) {
                    usepackage += "[" + options + "]";
                }
                usepackage += "{" + name + "}";
            }

            if (comment.isEmpty()) {
                retval.append(usepackage).append("\n");
            } else {
                retval.append(usepackage);
                for (int i=usepackage.length(); i<commentIndent; ++i) {
                    retval.append(" ");
                }
                retval.append(" % ").append(comment).append("\n");
            }
        }
        return retval.toString();
    }

    /**
     * Creates the file path/misc/style.sty, where path is
     * BookSettings.path(). File includes style definitions (fonts, templates, etc.)
     * for LaTeX.
     */
    void generateStyle() {
        final String indent = cs.indent();
        String title = bs.title().replace(" ", "_");
        String titlePrefix = title.equals("") ? "" : title + "/";

        final String[][] usedPackages = new String[][]{
            {"inputenc", "utf8"},
            {"amsmath"}, //
            {"listings", "", "code segments"}, //
            {"color", "", "links and code highlighting"}, //
            {"makeidx", "", "index"}, //
            {"hyperref", "pdftex", "external links and table", "external links and table of contents and index links"}, //
            {"graphicx", "", "images"}, //
            {"wrapfig", "", "thumbs"}, //
            {"subfig", "", "galleries"}, //
            {"verbatim", "", "multiline comments"}, //
            {"ulem", "", "strike-out text"}, //
            {"array", "", "more options with tables"}, //
            {}, // empty line
            {"mparhack", "", "fixes problems with margin paragraphs"}, //
            {"framed"}, //
            {"float", "", "for \\figure[H]\n\n"}, //
        };
        // also useful: alltt, multicol, longtable
        final String[][] polishPackages = new String[][] {
            {}, // empty line
            {"polski"}, //
            {"indentfirst"}, //
            {}, // empty line
            
        };

        String text = "";
        
        text += printPackages(usedPackages);
        // Polish-specific settings
        if (bs.getLanguage().equals("pl")) {
            text += printPackages(polishPackages);
        }

        text += "\\setcounter{secnumdepth}{0}" + indent + "% number chapters\n" +
                "\\setcounter{tocdepth}{1}" + indent + "% list chapters and sections\n\n" +
                "\\normalem\n" + "%\\makeindex\n\n";

        if (cs.smallerMargins) {
            text += "\\addtolength{\\hoffset}{-0.5cm}\n" +
                    "\\addtolength{\\textwidth}{1cm}\n\n";
        }

        text += "\\newcommand{\\titlefont}{\\fontsize{28}{30}\\selectfont}\n\n";

        text += "\\newcommand{\\wikipedia}[3][]{ % {{Wikipedia|1}} links in the margin\n" // {{Wikipedia|1}}
                + indent + "\\marginpar{\n" +
                indent + indent + messages.getString("see_in_Wikipedia") + "\n" +
                indent + indent + "#1\\href{http://" + bs.getLanguage() +
                ".wikipedia.org/wiki/#2}{#3}\n" + indent + "}\n" + "}\n\n";

        text += "\\newcommand{\\sidebox}[1]{ % {{sidebox|1}} in the margin\n" // {{sidebox|1}}
                + indent + "\\marginpar{#1}\n" + "}\n\n";

        text += "\\newcommand{\\textbox}[1]{ % {{textbox|1}} in a box\n" // {{textbox|1}}
                + indent + "\\fbox{#1}\n" + "}\n\n";

        // chapter links
        text += "\\newcommand{\\chapterlinks}[1]{ % create links to the chapter's Wikibooks page and talk in the " +
                "margin\n" + indent + "\\marginpar{\n" + indent + indent + "\\begin{itemize}\n";
        text += indent + indent + indent +
                String.format("\\item \\href{http://%s/wiki/%s#1}{%s}\n", bs.getServer(), titlePrefix, messages.getString("live_version")) +
                indent + indent + indent +
                String.format("\\item \\href{http://%s/wiki/Talk:%s#1}{%s}\n", bs.getServer(), titlePrefix, messages.getString("discussion")) +
                indent + indent + indent +
                String.format("\\item \\href{http://%s%sindex.php?title=%s#1\\&action=edit}{%s}\n", bs.getServer(), bs.getMainDir(), titlePrefix, messages.getString("edit")) +
                indent + indent + indent +
                String.format("\\item \\href{http://%s%sindex.php?title=Talk:%s#1\\&action=edit", bs.getServer(), bs.getMainDir(), titlePrefix) +
                String.format("\\&section=new}{%s}\n", messages.getString("comment")) + indent + indent + indent +
                String.format("\\item \\href{http://%s%sindex.php?title=Talk:%s#1\\&action=edit", bs.getServer(), bs.getMainDir(), titlePrefix) +
                String.format("\\&section=new}{%s}\n", messages.getString("report_an_error"));
        text += indent + indent + "\\end{itemize}\n" + indent + "}\n" + "}\n\n";

        // edit section link
        text += "\\newcommand{\\editsection}[2]{ % create [edit section] link in the margin: \\editsection{sub_page_name}" +
                "{sectionnumber}\n" + indent + "\\marginpar{\n" + indent + indent +
                "\\href{http://" + bs.getServer() + bs.getMainDir() + "index.php?title=" + titlePrefix + "#1\\&action=edit\\&section=#2}" +
                "{\\texttt{[edit section]}}\n" + indent + "}\n" + "}\n\n";

        // commands for templates in ConversionSettings.templateMap()
        for (String templateName : cs.templateMap().keySet()) {
            String commandName = cs.templateMap().get(templateName);
            if (!Util.LaTeXCommands().contains(commandName)) {
                String template = Util.downloadTemplate(templateName);
                template = new WikicodeConverterImpl(bs, cs).convert(template);
                template = Util.convertTemplateArguments(template);

                int arguments = 0; // set number of template arguments
                for (int i = 1; i < 10; i++) {
                    if (template.contains("#" + i)) {
                        arguments = i;
                    }
                }

                String formattedTemplate = "";
                for (String line : Util.lineArray(template)) {
                    formattedTemplate += indent + line + "\n";
                }

                text += "\\newcommand{\\" + commandName + "}[" + arguments + "]{ % {{{" + templateName + "}}}\n" + formattedTemplate + "}\n\n";
            }
        }

        text += "\\definecolor{uwagacolor}{rgb}{1.0,0.85,0.72}\n" +
                "\\definecolor{poradacolor}{rgb}{0.87,0.93,0.87}\n\n";
        text += "\\newenvironment{uwaga}{%\n" +
                "\\def\\FrameCommand{\\fboxsep=\\FrameSep \\colorbox{uwagacolor}}%\n" +
                "\\MakeFramed {\\FrameRestore}}%\n" +
                "{\\endMakeFramed}\n\n";
        text += "\\newenvironment{porada}{%\n" +
                "\\def\\FrameCommand{\\fboxsep=\\FrameSep \\colorbox{poradacolor}}%\n" +
                "\\MakeFramed {\\FrameRestore}}%\n" +
                "{\\endMakeFramed}\n\n";
        text += "\\newenvironment{infobox}{%\n" +
                "\\def\\FrameCommand{\\fboxsep=\\FrameSep \\colorbox{poradacolor}}%\n" +
                "\\MakeFramed {\\FrameRestore}}%\n" +
                "{\\endMakeFramed}\n\n";

        // links settings
        text += "% settings for links and PDF document info\n" +
                "\\definecolor{linkcolor}{rgb}{0.10,0.10,0.44}  % intra-document links are dark blue\n" +
                "\\definecolor{urlcolor}{rgb}{0.27,0.51,0.71}   % web links are light blue\n" +
                "\\hypersetup{pdftitle={" + title.replace("_", " ") + "},\n" + indent + "pdfauthor={" + bs.author() + "},\n" +
                indent + "pdfsubject=,\n" + indent + "pdfkeywords={},\n" + "%\n" + indent + "colorlinks=true,\n" +
                indent + "urlcolor=urlcolor,\n" + indent + "linkcolor=linkcolor\n" + "}";

        Util.writeToFile(text, new File(bs.path() + "misc/style.sty"));
    }

    /**
     * Creates the file path/appendices/docinfo.tex, where path is
     * BookSettings.path(). docinfo.tex lists book authors and links to the live
     * version of the book.
     */
    void generateDocInfo() {
        final boolean isBook = bs.article == null;
        final DateFormat dateFormatter = new SimpleDateFormat("d MMMM yyyy", new Locale(bs.getLanguage()));
        final Date today = new Date();

        final String heading = isBook ? "chapter" : "section";
        final String subheading = isBook ? "section" : "subsection";

        String title = bs.title().replace(" ", "_");
        StringBuilder text = new StringBuilder();

        text.append(String.format("\\%s[%s]{%s}\n\n", heading,
                messages.getString("document_information"), messages.getString("document_information_and_history")));

        text.append(String.format("\\%s{%s}\n\n", subheading, messages.getString("section_history")));
        text.append(messages.getString("this_book_was_created")).append(bs.generateAuthorsList ? messages.getString("by_the_contributors") : "");
        text.append(String.format(messages.getString("for_convenience"), bs.getServer(), title));

        text.append(String.format("\\%s{%s}\n\n", subheading, messages.getString("pdf_information_and_history")));
        text.append(String.format(messages.getString("compiled_from"), bs.getServer() + ".org/wiki/" + title,
                dateFormatter.format(today), bs.getServer() + ".org/wiki/" + title));
        text.append(String.format(messages.getString("the_latest_version"), title));

        if (bs.generateAuthorsList) {
            text.append(String.format("\\%s{%s}\\label{sec:authors}\n\n", subheading, messages.getString("section_authors")));
            SortedSet<String> authors = getBookAuthors();

            for (String author : authors) {
                if (!author.contains(".")) // remove IPs
                {
                    text.append(String.format("\\href{http://%s.org/wiki/User:%s}{%s}, ", bs.getServer(), author, author));
                }
            }
            text.append(messages.getString("and_anonymous_contributors"));
        }

        Util.writeToFile(text.toString(), new File(bs.path() + "appendices/docinfo.tex"));
    }

    /**
     * Creates the file path/book.kilepr, where path is BookSettings.path().
     * Open book.kilepr with the Kile LaTeX IDE to edit the generated files
     * easily.
     */
    void generateKileProjectFile() {
        String text = "[General]\n";
        text += "img_extIsRegExp=false\n" + "img_extensions=.eps .pdf .dvi .ps .fig .gif .jpg .jpeg .png\n" +
                "kileprversion=1\n" + "kileversion=1.9.3\n" + "lastDocument=main.tex\n" + "masterDocument=main.tex\n" +
                "name=book\n" + "pkg_extIsRegExp=false\n" + "pkg_extensions=.cls .sty .dtx\n" +
                "src_extIsRegExp=false\n" + "src_extensions=.tex .ltx .bib .mp\n\n";
        text += "[Tools]\n" + "MakeIndex=\n" + "QuickBuild=\n\n";

        if (bs.generateMainFile) {
            text += "[item:main.tex]\n" + "archive=true\n" + "column=0\n" + "encoding=UTF-8\n" +
                    "highlight=LaTeX\n" + "line=0\n" + "open=true\n\n";
        }

        ArrayList<String> items = new ArrayList<String>();
        if (bs.generateTitlePage) {
            items.add("misc/style.sty");
        }
        if (bs.generateStyle) {
            items.add("misc/titlepage.tex");
        }
        if (bs.generateDocInfo) {
            items.add("appendices/docinfo.tex");
        }
        items.add("appendices/gfdl.tex");
        for (String item : items) {
            text += "[item:" + item + "]\n" + "archive=true\n" + "column=0\n" + "encoding=UTF-8\n" +
                    "highlight=LaTeX\n" + "line=0\n" + "open=false\n\n";
        }

        for (String[] chapter : bs.chapters()) {
            text += "[item:chapters/" + chapter[0] + ".tex]\n" + "archive=true\n" + "column=0\n" + "encoding=UTF-8\n" +
                    "highlight=LaTeX\n" + "line=0\n" + "open=false\n\n";
        }

        Util.writeToFile(text, new File(bs.path() + "book.kilepr"));
    }

    /**
     * Creates the file path/wikipage.txt, where path is BookSettings.path().
     * Copy the file to [[Book Title/LaTeX]]
     * (http://en.wikibooks.org/wiki/Book_Title/LaTeX).
     */
    void generateWikiPage() {
        String text = "<!-- Copy this to [[" + bs.title() + "/LaTeX]] (http://en.wikibooks.org/wiki/" + bs.title() + "/LaTeX]]). -->";

        text += "<!-- TODO -->";

        Util.writeToFile(text, new File(bs.path() + "wikipage.txt"));
    }
}
